package kubernetes

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/util"
)

type NodeController struct{}

func (c NodeController) ListNode(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}

	q := r.URL.Query()
	labelSelectors := parseLabelSelector(q.Get(labelSelector))

	delete(labelSelectors, groupIDKey)
	delete(labelSelectors, userIDKey)
	largestRole := authapi.GetLargestRolePermission(info.Role)
	switch largestRole {
	case authapi.OpServiceRole:
		break
	case authapi.AdminRole:
		fallthrough
	case authapi.UserRole:
		fallthrough
	default:
		labelSelectors[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)
	}

	if len(labelSelectors) != 0 {
		q.Del(labelSelector)
		q.Add(labelSelector, constructLabelSelector(labelSelectors))
		r.URL.RawQuery = q.Encode()
	}

	(&KubernetesController{}).ServeHTTP(w, r)
}

func (c NodeController) getNode(name string) (*corev1.Node, error) {
	client := util.GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/nodes/%s", kubernetesAddress, name), nil)
	if err != nil {
		glog.Errorf("construct request to get node failed, err: %v", err)
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get node failed, err: %v", err)
		return nil, err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, util.NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, node name: %v, err: %v", name, err)
			return nil, fmt.Errorf("get node failed")
		}
		return nil, fmt.Errorf("%v", status.Message)
	}

	node := corev1.Node{}
	err = json.Unmarshal(requestBody, &node)
	if err != nil {
		glog.Errorf("unmarshal response body failed, node name: %v, err: %v", name, err)
		return nil, fmt.Errorf("get node failed")
	}

	return &node, nil
}

func (c NodeController) GetNode(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	nodeName := mux.Vars(r)["name"]
	node, err := c.getNode(nodeName)
	if err == util.NotFoundError {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	if !(node.Labels[groupIDKey] == strconv.Itoa(info.Group.ID) || largestRole == authapi.OpServiceRole) {
		errorMessage := fmt.Sprintf("no permission to get node %v", nodeName)
		glog.Errorf("%v, node group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			node.Labels[groupIDKey], node.Labels[userNameKey], node.Labels[userIDKey],
			info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	respBody, _ := json.Marshal(node)
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (c NodeController) DeleteNode(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)
	if largestRole == authapi.UserRole {
		errorMessage := "ordinary users are not allowed to delete node"
		glog.Errorf(errorMessage)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	nodeName := mux.Vars(r)["name"]
	if largestRole == authapi.OpServiceRole {
		glog.Infof("start delete node, node name: %v, user: %v/%v, user role: %v", nodeName, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	node, err := c.getNode(nodeName)
	if err == util.NotFoundError {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}
	if node.Labels[groupIDKey] == strconv.Itoa(info.Group.ID) && largestRole == authapi.AdminRole {
		glog.Infof("start delete node, node name: %v, user: %v/%v, user role: %v", nodeName, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	errorMessage := fmt.Sprintf("no permission to delete node %v", nodeName)
	glog.Errorf("%v, node group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
		node.Labels[groupIDKey], node.Labels[userNameKey], node.Labels[userIDKey],
		info.Name, info.UserID, info.Group.ID, largestRole)
	util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
	return
}

func (c NodeController) UpdateNode(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)
	if largestRole == authapi.UserRole {
		errorMessage := "ordinary users are not allowed to update node"
		glog.Errorf(errorMessage)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	nodeName := mux.Vars(r)["name"]
	var oldNode *corev1.Node
	oldNode, err = c.getNode(nodeName)
	if err == util.NotFoundError {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get node from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	if largestRole != authapi.OpServiceRole && !(oldNode.Labels[groupIDKey] == strconv.Itoa(info.Group.ID) && largestRole == authapi.AdminRole) {
		errorMessage := fmt.Sprintf("no permission to update node %v", nodeName)
		glog.Errorf("%v, node group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			oldNode.Labels[groupIDKey], oldNode.Labels[userNameKey], oldNode.Labels[userIDKey],
			info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}

	node := corev1.Node{}
	err = json.Unmarshal(requestBody, &node)
	if err != nil {
		glog.Errorf("unmarshal request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("unmarshal request body failed, %v", err))
		return
	}

	if node.Labels == nil {
		node.Labels = make(map[string]string)
	}
	if oldNode.Labels[groupIDKey] == "" && node.Labels[clusterNameKey] != "" {
		// 未分配的节点，在分配至集群时，同步打上其他标签
		node.Labels[userIDKey] = fmt.Sprintf("%v", info.UserID)
		node.Labels[userNameKey] = fmt.Sprintf("%v", info.Name)
		node.Labels[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)
	}
	if node.Labels[groupIDKey] != "" && oldNode.Labels[groupIDKey] != "" && node.Labels[groupIDKey] != oldNode.Labels[groupIDKey] {
		glog.Errorf("node shouldn't be changed its group, node name: %v, old group id: %v, new group id: %v, user: %v/%v", nodeName, oldNode.Labels[groupIDKey], node.Labels[groupIDKey], info.Name, info.UserID)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, "node shouldn't be changed its group")
		return
	}
	if node.Labels[clusterNameKey] != "" && oldNode.Labels[clusterNameKey] != "" && node.Labels[clusterNameKey] != oldNode.Labels[clusterNameKey] {
		glog.Errorf("node shouldn't be changed its cluster, node name: %v, old group id: %v, new group id: %v, user: %v/%v", nodeName, oldNode.Labels[groupIDKey], node.Labels[groupIDKey], info.Name, info.UserID)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, "node shouldn't be changed its cluster")
		return
	}

	newRequestBody, _ := json.Marshal(node)

	req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(newRequestBody))
	if err != nil {
		glog.Errorf("construct request failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("construct request failed, %v", err))
		return
	}
	req.RequestURI = r.RequestURI
	req.Header.Set("Content-Type", "application/merge-patch+json")

	glog.Infof("start update node, node name: %v, user: %v/%v, user role: %v", nodeName, info.Name, info.UserID, largestRole)
	(&KubernetesController{}).ServeHTTP(w, req)
	return
}
